"""
Functions to deal with creating, accessing, and modifying IP table databases.
"""

import sqlite3, os

from django.conf import settings

from jackson import debug
from jackson.chord.db import get_me
from jackson.chord import Server
from jackson.cdn.client_api import register_key, get_key_status

import jackson.extjson.ext_json as json
from random import choice
from cdn.models import ContentEntry

ALPHA_RTT = .875


def list_ip_dbs():
    """
    Returns a list of keys (which are filenames) of IP table dbs we have on disk
    """
    context = [__name__, 'list_ip_keys()']
    debug.enter(context, None)
    debug.comment(context, 'Querying away')
    qs = ContentEntry.objects.filter(mimetype='IP')
    ip_list = []
    for entry in qs:
        debug.comment(context, 'Found table for ' + entry.filename)
        ip_list.append((entry.key, entry.filename))
    debug.leave(context, { 'ip_list': ip_list })
    return ip_list


def create_IP_table(key,cstr):
    """
    Function creates a table named 'ip' in the SQLite database file named key
    @param key: the filename which should be the SQLite database where we want
    to create the 'ip' table
    """
    context = [__name__, "create_IP_table()"]
    debug.enter(context, {'key': key})
    me = get_me()

    doc_root = settings.LIGHTTPD_DOC_ROOT
    sqlite3.register_adapter(Server, json.dumps)
    sqlite3.register_converter("server", json.loads)
    
    # @note: remove this at production time
    # useful for debugging to remove the old database file when recreating
    if os.path.exists(doc_root + key):
        os.remove(doc_root + key)
    
    con = sqlite3.connect(doc_root + key, detect_types=sqlite3.PARSE_DECLTYPES)
    con.execute("create table ip(serv server primary key, rtt real)")
    con.commit()
    con.close()
    s = get_key_status(key)
    if s in ["H","O"]:
        debug.comment(context,"create_IP_table() is registering a key that was already here")
        debug.comment(context,"this may be an issue.")
    url = "http://" + me.ip + ":" + str(me.port) + "/" + key
    debug.comment(context,"registering the database with cdn, at url: " + url)
    #debug.comment(context,"NOT SUPPLYING REGISTER_KEY() WITH A FILENAME")

    mimer = 'IP'
    debug.comment(context,"SUPPLYING REGISTER_KEY() WITH MIMETYPE: " + mimer)
    register_key(key, url, new_filename=cstr, new_mimetype=mimer)
    debug.leave(context, {'key': key})


def database_exists(key):
    """
    Checks whether a database file exists on disk
    """
    context = [__name__, "database_exists()"]
    debug.enter(context, None,4)
    b = os.path.exists(settings.LIGHTTPD_DOC_ROOT + key)
    debug.leave(context, None,4)
    return b

def update_IP_table(key, server, rtt):
    """
    Function updates the RTT of the given server for the given IP.
    @param key: Hashed CIDRized IP
    @param RTT: round-trip time
    @param server: A server object representing the server
    """
    context = [__name__, "update_IP_table()"]
    debug.enter(context, {'key': key, 'rtt': rtt, 'server': server})
    
    sqlite3.register_adapter(Server, json.dumps)
    sqlite3.register_converter("server", json.loads)
    doc_root = settings.LIGHTTPD_DOC_ROOT
    fname = doc_root + key

    try:
        debug.comment(context,"check to see if the database is already there")
        if not database_exists(key):
            estring = 'Database with key ' + key + ' does not exist.'
            debug.exception(context, estring)
        debug.comment(context,"opening the connection")
        con = sqlite3.connect(fname, detect_types=sqlite3.PARSE_DECLTYPES)
        debug.comment(context,"calling into the database via execute()")
        a = con.execute("select name from sqlite_master where name='ip'").fetchall()
        debug.comment(context,"taking the length of this thing")
        ip_exists = len(a)
        if not ip_exists:
            estring = 'Database file ' + key + " does not have an 'ip' table"
            debug.exception(context, estring)
        debug.comment(context,"grabbing some stuff")
        rtts = con.execute("select rtt from ip where serv=?", (server,))
        debug.comment(context,"fetching all")
        rtts = rtts.fetchall()
        debug.comment(context,"rtts: " + str(rtts))
        if len(rtts) == 0:
            con.execute("insert into ip(serv, rtt) values (?,?)", (server, rtt))
            con.commit()
        else:
            new_rtt = rtts[0][0]
            # update with an alpha value heavily weighted towards the old value
            rtt = ALPHA_RTT*rtt + (1 - ALPHA_RTT)*new_rtt
            con.execute("update ip set rtt=? where serv=?", (rtt, server))
            con.commit()
        con.close()
    except Exception, e:
        debug.exception(context, str(e))
    debug.leave(context, None)


def delete_server(key, server):
    """
    Deletes entries for the given server from the given IP table
    @param server: the server to delete
    """
    context = [__name__, "delete_server()"]
    debug.enter(context, {'key': key, 'server': server})
    doc_root = settings.LIGHTTPD_DOC_ROOT
    fname = doc_root + key
    try:
        if not database_exists(fname):
            estring = 'IP table at filename: '+ fname + ', does not exist.'
            debug.exception(context, estring)
            raise IOError(estring)
        con = sqlite3.connect(fname, detect_types=sqlite3.PARSE_DECLTYPES)
        ip_exists = len(con.execute("select name from sqlite_master where name='ip'").fetchall())
        if not ip_exists:
            estring = 'Database at filename: ' + filename + " does not have an 'ip' table"
            debug.exception(context, estring)
            raise ValueError(estring)
        con.execute("delete from ip where serv=?", (server,))
        con.commit()
        con.close()
    except Exception, e:
        debug.exception(context, str(e))
    debug.leave(context,None)


def lookup_IP_table(key):
    """
    Function returns the RTTs and their associated servers for the given IP
    represented by the given key. The database for the given key should exist,
    otherwise, throws and exception.
    @param key: the hashed CIDRized IP value for which to get data
    @rtype: a list of 2-tuples where the first element should be a Server object
    and the second element should be a float (the rtt)
    """
    context = [__name__, "lookup_IP_table()"]
    debug.enter(context, {'key': key})

    doc_root = settings.LIGHTTPD_DOC_ROOT
    fname = doc_root + key
    try:
        if not os.path.exists(fname):
            estring = 'database file for IP table at path: '+    \
                      doc_root + key + ', does not exist on disk.'
            debug.exception(context, estring)
        con = sqlite3.connect(fname, detect_types=sqlite3.PARSE_DECLTYPES)
        ip_exists = len(con.execute("select name from sqlite_master where name='ip'").fetchall())
        if not ip_exists:
            estring = 'Database at filename: ' + fname + ", does not have an 'ip' table"
            debug.exception(context, estring)
        rows = con.execute("select * from ip").fetchall()
        con.close()
    except Exception, e:
        debug.exception(context, str(e))
        debug.leave(context,None)
        rows = []
    debug.leave(context,{"rows":rows})
    return rows

def minimum_RTT_from_IP_table(key):
    """ a small helper function. returns the fastest server for a given key
    @rtype: (Server,rtt)
    """
    context = [__name__, "minimum_RTT_from_IP_table()"]
    debug.enter(context,None)
    server_rtt_pair = False
    try:
        server_rtt_tuple_list = lookup_IP_table(key)
        # grab the lowest RTT from the ones we are given
        min_rtt = min([rtt for (server,rtt) in server_rtt_tuple_list])
        # pick one at random that has the min value
        min_rtt_servers = [pair for pair in 
                           server_rtt_tuple_list if pair[1] == min_rtt]
        server_rtt_pair = choice(min_rtt_servers)
    except Exception, e:
        a = False
        debug.exception(context,"problem! " + str(e))
    debug.leave(context,None)
    return server_rtt_pair


